///\todo upgrade

//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef _BUOLA_IMAGE_CPIXEL_H_
#define _BUOLA_IMAGE_CPIXEL_H_

#include <buola/image.h>
#include <buola/image/ccolorspaceconversion.h>

namespace buola { namespace img {

DECLARE_NAME(Black)

template<typename... tTypes>
class CPixelImmediate
{
public:
    CPixelImmediate(tTypes... pData)
        :   mData(pData...)
    {}

    const std::tuple<tTypes...> &Data() const {   return mData;   }

private:
    std::tuple<tTypes...> mData;
};

template<typename... tTypes>
inline CPixelImmediate<tTypes...> pixel(tTypes&&... pVals)
{
    return CPixelImmediate<tTypes...>(std::forward<tTypes>(pVals)...);
}

template<typename tType,std::size_t pChannels>
class CPixelBase
{
protected:
    template<int i>
    void Assign()
    {}
    
    template<int i,typename... tArgs>
    void Assign(tType pType,const tArgs&... pArgs)
    {
        mData[i]=pType;
        Assign<i+1>(pArgs...);
    }

    template<int i,typename... tTypes>
    typename std::enable_if<(i>=pChannels),void>::type
    AssignTuple(const std::tuple<tTypes...>&)
    {}
    
    template<int i,typename... tTypes>
    typename std::enable_if<(i<pChannels),void>::type
    AssignTuple(const std::tuple<tTypes...> &pTuple)
    {
        mData[i]=std::get<i>(pTuple);
        AssignTuple<i+1>(pTuple);
    }

protected:
    CPixelBase()=default;

public:
    CPixelBase(const CPixelBase&)=default;
    
    template<typename... tArgs>
    CPixelBase(const tArgs&... pArgs)
    {
        static_assert(sizeof...(pArgs)==pChannels,"wrong number of arguments in CPixel");
        Assign<0>(pArgs...);
    }

    template<typename... tTypes>
    CPixelBase(const CPixelImmediate<tTypes...> &pImm)
    {
        static_assert(sizeof...(tTypes)==pChannels,"wrong number of channels in CPixelImmediate");
        AssignTuple<0>(pImm.Data());
    }

public:
    tType &operator[](int pC)
    {
        return mData[pC];
    }
    
    const tType &operator[](int pC) const
    {
        return mData[pC];
    }
    
private:
    tType mData[pChannels];
};
    
template<typename tType,typename tFmt,std::size_t vChannels=tFmt::TColorSpace::cChannels>
class CPixelAccessor
{
public:
    typedef CPixelBase<ORemoveCV<tType>,vChannels> TPixel;
    typedef typename tFmt::TChannel TChannel;
    CPixelAccessor(tType *pPtr)
        :   mPtr(pPtr)
    {}
    
    CPixelAccessor(const CPixelAccessor&)=default;
    
    tType &operator[](int pC) const
    {
        return mPtr[pC];
    }
    
    const CPixelAccessor &operator=(const CPixelAccessor &pRH) const
    {
        for(int i=0;i<vChannels;i++)
            mPtr[i]=pRH.mPtr[i];
        return *this;
    }
    
    const CPixelAccessor &operator=(const TPixel &pRH) const
    {
        for(int i=0;i<vChannels;i++)
            mPtr[i]=pRH[i];
        return *this;
    }

    void Blend(const CPixelAccessor &pRH,uint8_t pCoverage) const
    {
        tFmt::TColorSpace::Blend(mPtr,&pRH[0],pCoverage);
    }

    void Blend(const TPixel &pRH,uint8_t pCoverage) const
    {
        tFmt::TColorSpace::Blend(mPtr,&pRH[0],pCoverage);
    }
    
    bool operator==(const TPixel &pRH) const
    {
        for(int i=0;i<vChannels;i++)
            if(mPtr[i]!=pRH[i]) return false;
        return true;
        
    }
    
    bool operator!=(const TPixel &pRH) const
    {
        return !operator==(pRH);
    }
    
    tType *operator&() const
    {
        return mPtr;
    }
    
    
private:
    tType *mPtr;
};
    
template<typename tType,typename tFmt>
class CPixelAccessor<tType,tFmt,1>
{
public:
    typedef CPixelBase<ORemoveCV<tType>,1> TPixel;
    typedef typename tFmt::TChannel TChannel;

    CPixelAccessor(tType *pPtr)
        :   mPtr(pPtr)
    {}
    
    CPixelAccessor(const CPixelAccessor&)=default;
    
    tType &operator[](int pC) const
    {
        return mPtr[pC];
    }
    
    const CPixelAccessor &operator=(const CPixelAccessor &pRH) const
    {
        *mPtr=*pRH.mPtr;
        return *this;
    }
    
    const CPixelAccessor &operator=(const tType &pRH) const
    {
        *mPtr=pRH;
        return *this;
    }
    
    const CPixelAccessor &operator=(const TPixel &pRH) const
    {
        *mPtr=pRH[0];
        return *this;
    }
    
    operator const tType&() const
    {
        return *mPtr;
    }
    
    tType *operator&() const
    {
        return mPtr;
    }
    
    tType *operator->() const
    {
        return mPtr;
    }
private:
    tType *mPtr;
};
    
template<typename tType,typename tFormat,int vPixelAdvance>
class CPixelXIterator
{
public:
    typedef CPixelAccessor<tType,tFormat> TAccessor;
    static const std::size_t mAdvance=tFormat::TColorSpace::cChannels*vPixelAdvance;
    
    CPixelXIterator()=default;
    CPixelXIterator(const CPixelXIterator&)=default;
    
    CPixelXIterator(tType *pPtr)
        :   mPtr(pPtr)
    {}
    
    ~CPixelXIterator()=default;

    CPixelXIterator &operator=(const CPixelXIterator&)=default;

    CPixelXIterator &operator++()
    {
        mPtr+=mAdvance;
        return *this;
    }
    
    CPixelXIterator operator++(int)
    {
        CPixelXIterator lI(*this);
        mPtr+=mAdvance;
        return lI;
    }
    
    CPixelXIterator &operator--()
    {
        mPtr-=mAdvance;
        return *this;
    }
    
    CPixelXIterator operator--(int)
    {
        CPixelXIterator lI(*this);
        mPtr-=mAdvance;
        return lI;
    }
    
    CPixelXIterator operator+(std::ptrdiff_t pD) const
    {
        return CPixelXIterator(mPtr+pD*mAdvance);
    }
    
    CPixelXIterator operator-(std::ptrdiff_t pD) const
    {
        return CPixelXIterator(mPtr-pD*mAdvance);
    }
    
    CPixelXIterator &operator+=(std::ptrdiff_t pD)
    {
        mPtr+=pD*mAdvance;
        return *this;
    }
    
    CPixelXIterator &operator-=(std::ptrdiff_t pD)
    {
        mPtr-=pD*mAdvance;
        return *this;
    }
    
    TAccessor operator[](std::ptrdiff_t pD) const
    {
        return TAccessor(mPtr+pD*mAdvance);
    }
    
    TAccessor operator*() const
    {
        return TAccessor(mPtr);
    }

    bool operator==(const CPixelXIterator &pRH) const
    {
        return (mPtr==pRH.mPtr);
    }

    bool operator!=(const CPixelXIterator &pRH) const
    {
        return (mPtr!=pRH.mPtr);
    }

    bool operator<(const CPixelXIterator &pRH) const
    {
        return (mPtr<pRH.mPtr);
    }

    bool operator>(const CPixelXIterator &pRH) const
    {
        return (mPtr>pRH.mPtr);
    }

    bool operator<=(const CPixelXIterator &pRH) const
    {
        return (mPtr<=pRH.mPtr);
    }

    bool operator>=(const CPixelXIterator &pRH) const
    {
        return (mPtr>=pRH.mPtr);
    }

    tType *GetPointer() const
    {
        return mPtr;
    }

private:
    tType *mPtr;
};

///\todo make TAccessor a member, so that it is possible to have an operator->. Check that it doesn't affect performance

template<typename tType,typename tFormat>
class CPixelYIterator
{
public:
    typedef CPixelAccessor<tType,tFormat> TAccessor;
    
    CPixelYIterator()=default;
    CPixelYIterator(const CPixelYIterator&)=default;
    
    CPixelYIterator(tType *pPtr,ptrdiff_t pStride)
        :   mPtr((unsigned char*)pPtr)
        ,   mStride(pStride)
    {}
    
    ~CPixelYIterator()=default;

    CPixelYIterator &operator=(const CPixelYIterator&)=default;

    CPixelYIterator &operator++()
    {
        mPtr+=mStride;
        return *this;
    }
    
    CPixelYIterator operator++(int)
    {
        CPixelYIterator lI(*this);
        mPtr+=mStride;
        return lI;
    }
    
    CPixelYIterator &operator--()
    {
        mPtr-=mStride;
        return *this;
    }
    
    CPixelYIterator operator--(int)
    {
        CPixelYIterator lI(*this);
        mPtr-=mStride;
        return lI;
    }
    
    CPixelYIterator operator+(std::ptrdiff_t pD) const
    {
        return CPixelYIterator(mPtr+pD*mStride,mStride);
    }
    
    CPixelYIterator operator-(std::ptrdiff_t pD) const
    {
        return CPixelYIterator(mPtr-pD*mStride,mStride);
    }
    
    CPixelYIterator &operator+=(std::ptrdiff_t pD)
    {
        mPtr+=pD*mStride;
        return *this;
    }
    
    CPixelYIterator &operator-=(std::ptrdiff_t pD)
    {
        mPtr-=pD*mStride;
        return *this;
    }
    
    TAccessor operator[](std::ptrdiff_t pD) const
    {
        return TAccessor((tType*)(mPtr+pD*mStride));
    }
    
    TAccessor operator*() const
    {
        return TAccessor((tType*)mPtr);
    }

    bool operator==(const CPixelYIterator &pRH) const
    {
        return (mPtr==pRH.mPtr);
    }

    bool operator!=(const CPixelYIterator &pRH) const
    {
        return (mPtr!=pRH.mPtr);
    }

    bool operator<(const CPixelYIterator &pRH) const
    {
        return (mPtr<pRH.mPtr);
    }

    bool operator>(const CPixelYIterator &pRH) const
    {
        return (mPtr>pRH.mPtr);
    }

    bool operator<=(const CPixelYIterator &pRH) const
    {
        return (mPtr<=pRH.mPtr);
    }

    bool operator>=(const CPixelYIterator &pRH) const
    {
        return (mPtr>=pRH.mPtr);
    }

    tType *GetPointer() const
    {
        return (tType*)mPtr;
    }

private:
    unsigned char *mPtr;
    ptrdiff_t mStride;
};

template<typename tAccessor,typename tAdvance>
class CPixelIterator : private tAdvance
{
public:
    CPixelIterator()=default;
    CPixelIterator(const CPixelIterator&)=default;

    CPixelIterator(const tAccessor &pAccessor,const tAdvance &pAdvance)
        :   tAdvance(pAdvance)
        ,   mAccessor(pAccessor)
    {}

    ~CPixelIterator()=default;

    CPixelIterator &operator=(const CPixelIterator&)=default;

    CPixelIterator &operator++()
    {
        tAdvance::Inc(mAccessor);
        return *this;
    }

    CPixelIterator operator++(int)
    {
        CPixelIterator lI(*this);
        tAdvance::Inc(mAccessor);
        return lI;
    }

    CPixelIterator &operator--()
    {
        tAdvance::Dec(mAccessor);
        return *this;
    }

    CPixelIterator operator--(int)
    {
        CPixelIterator lI(*this);
        tAdvance::Dec(mAccessor);
        return lI;
    }

    CPixelIterator &operator+=(std::ptrdiff_t pD)
    {
        tAdvance::Inc(mAccessor,pD);
        return *this;
    }

    CPixelIterator &operator-=(std::ptrdiff_t pD)
    {
        tAdvance::Dec(mAccessor,pD);
        return *this;
    }

    CPixelIterator operator+(std::ptrdiff_t pD) const
    {
        CPixelIterator lI(*this);
        lI+=pD;
        return lI;
    }

    CPixelIterator operator-(std::ptrdiff_t pD) const
    {
        CPixelIterator lI(*this);
        lI-=pD;
        return lI;
    }

    tAccessor operator[](std::ptrdiff_t pD) const
    {
        return tAdvance::At(mAccessor,pD);
    }

    const tAccessor &operator*() const
    {
        return mAccessor;
    }

    const tAccessor *operator->() const
    {
        return &mAccessor;
    }

    bool operator==(const CPixelIterator &pRH) const
    {
        return tAdvance::Equal(mAccessor,pRH.mAccessor);
    }

    bool operator!=(const CPixelIterator &pRH) const
    {
        return !tAdvance::Equal(mAccessor,pRH.mAccessor);
    }

    bool operator<(const CPixelIterator &pRH) const
    {
        return tAdvance::Less(mAccessor,pRH.mAccessor);
    }

    bool operator>(const CPixelIterator &pRH) const
    {
        return tAdvance::Less(pRH.mAccessor,mAccessor);
    }

    bool operator<=(const CPixelIterator &pRH) const
    {
        return !tAdvance::Less(pRH.mAccessor,mAccessor);
    }

    bool operator>=(const CPixelIterator &pRH) const
    {
        return !tAdvance::Less(mAccessor,pRH.mAccessor);
    }

private:
    tAccessor mAccessor;
};

template<typename tFormat>
class CPixel : public CPixelBase<typename tFormat::TChannel::TType,tFormat::TColorSpace::cChannels>
{
    typedef CPixelBase<typename tFormat::TChannel::TType,tFormat::TColorSpace::cChannels> tBase;
public:
    typedef typename tFormat::TChannel TChannel;

    CPixel()=default;
    CPixel(const CPixel&)=default;

    template<typename... tArgs>
    CPixel(const tArgs&... pArgs)
        :   tBase(pArgs...)
    {}

    template<typename... tTypes>
    CPixel(const CPixelImmediate<tTypes...> &pImm)
        :   tBase(pImm)
    {}

    CPixel(const CPixelAccessor<typename TChannel::TType,tFormat> &pPixel)
    {
        for(int i=0;i<tFormat::TColorSpace::cChannels;i++)
            this->operator[](i)=pPixel[i];
    }

    CPixel(const CPixelAccessor<const typename TChannel::TType,tFormat> &pPixel)
    {
        for(int i=0;i<tFormat::TColorSpace::cChannels;i++)
            this->operator[](i)=pPixel[i];
    }

    CPixel(const CColor &pColor);

    CPixel(NBlack)
    {
        for(int i=0;i<tFormat::TColorSpace::cChannels;i++)
            this->operator[](i)=0;
    }

    CPixel &operator=(const CPixelAccessor<typename TChannel::TType,tFormat> &pPixel)
    {
        for(int i=0;i<tFormat::TColorSpace::cChannels;i++)
            this->operator[](i)=pPixel[i];
        return *this;
    }

    CPixel &operator=(const CPixelAccessor<const typename TChannel::TType,tFormat> &pPixel)
    {
        for(int i=0;i<tFormat::TColorSpace::cChannels;i++)
            this->operator[](i)=pPixel[i];
        return *this;
    }
};

/*namespace img*/ } /*namespace buola*/ }

#endif
